// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function(mod) {
  if (typeof exports == 'object' && typeof module == 'object')
    // CommonJS
    mod(
      require('../../lib/codemirror'),
      require('../htmlmixed/htmlmixed'),
      require('../ruby/ruby'),
    );
  else if (typeof define == 'function' && define.amd)
    // AMD
    define([
      '../../lib/codemirror',
      '../htmlmixed/htmlmixed',
      '../ruby/ruby',
    ], mod);
  // Plain browser env
  else mod(CodeMirror);
})(function(CodeMirror) {
  'use strict';

  // full haml mode. This handled embedded ruby and html fragments too
  CodeMirror.defineMode(
    'haml',
    function(config) {
      var htmlMode = CodeMirror.getMode(config, { name: 'htmlmixed' });
      var rubyMode = CodeMirror.getMode(config, 'ruby');

      function rubyInQuote(endQuote) {
        return function(stream, state) {
          var ch = stream.peek();
          if (ch == endQuote && state.rubyState.tokenize.length == 1) {
            // step out of ruby context as it seems to complete processing all the braces
            stream.next();
            state.tokenize = html;
            return 'closeAttributeTag';
          } else {
            return ruby(stream, state);
          }
        };
      }

      function ruby(stream, state) {
        if (stream.match('-#')) {
          stream.skipToEnd();
          return 'comment';
        }
        return rubyMode.token(stream, state.rubyState);
      }

      function html(stream, state) {
        var ch = stream.peek();

        // handle haml declarations. All declarations that cant be handled here
        // will be passed to html mode
        if (state.previousToken.style == 'comment') {
          if (state.indented > state.previousToken.indented) {
            stream.skipToEnd();
            return 'commentLine';
          }
        }

        if (state.startOfLine) {
          if (ch == '!' && stream.match('!!')) {
            stream.skipToEnd();
            return 'tag';
          } else if (stream.match(/^%[\w:#\.]+=/)) {
            state.tokenize = ruby;
            return 'hamlTag';
          } else if (stream.match(/^%[\w:]+/)) {
            return 'hamlTag';
          } else if (ch == '/') {
            stream.skipToEnd();
            return 'comment';
          }
        }

        if (state.startOfLine || state.previousToken.style == 'hamlTag') {
          if (ch == '#' || ch == '.') {
            stream.match(/[\w-#\.]*/);
            return 'hamlAttribute';
          }
        }

        // donot handle --> as valid ruby, make it HTML close comment instead
        if (
          state.startOfLine &&
          !stream.match('-->', false) &&
          (ch == '=' || ch == '-')
        ) {
          state.tokenize = ruby;
          return state.tokenize(stream, state);
        }

        if (
          state.previousToken.style == 'hamlTag' ||
          state.previousToken.style == 'closeAttributeTag' ||
          state.previousToken.style == 'hamlAttribute'
        ) {
          if (ch == '(') {
            state.tokenize = rubyInQuote(')');
            return state.tokenize(stream, state);
          } else if (ch == '{') {
            if (!stream.match(/^\{%.*/)) {
              state.tokenize = rubyInQuote('}');
              return state.tokenize(stream, state);
            }
          }
        }

        return htmlMode.token(stream, state.htmlState);
      }

      return {
        // default to html mode
        startState: function() {
          var htmlState = CodeMirror.startState(htmlMode);
          var rubyState = CodeMirror.startState(rubyMode);
          return {
            htmlState: htmlState,
            rubyState: rubyState,
            indented: 0,
            previousToken: { style: null, indented: 0 },
            tokenize: html,
          };
        },

        copyState: function(state) {
          return {
            htmlState: CodeMirror.copyState(htmlMode, state.htmlState),
            rubyState: CodeMirror.copyState(rubyMode, state.rubyState),
            indented: state.indented,
            previousToken: state.previousToken,
            tokenize: state.tokenize,
          };
        },

        token: function(stream, state) {
          if (stream.sol()) {
            state.indented = stream.indentation();
            state.startOfLine = true;
          }
          if (stream.eatSpace()) return null;
          var style = state.tokenize(stream, state);
          state.startOfLine = false;
          // dont record comment line as we only want to measure comment line with
          // the opening comment block
          if (style && style != 'commentLine') {
            state.previousToken = { style: style, indented: state.indented };
          }
          // if current state is ruby and the previous token is not `,` reset the
          // tokenize to html
          if (stream.eol() && state.tokenize == ruby) {
            stream.backUp(1);
            var ch = stream.peek();
            stream.next();
            if (ch && ch != ',') {
              state.tokenize = html;
            }
          }
          // reprocess some of the specific style tag when finish setting previousToken
          if (style == 'hamlTag') {
            style = 'tag';
          } else if (style == 'commentLine') {
            style = 'comment';
          } else if (style == 'hamlAttribute') {
            style = 'attribute';
          } else if (style == 'closeAttributeTag') {
            style = null;
          }
          return style;
        },
      };
    },
    'htmlmixed',
    'ruby',
  );

  CodeMirror.defineMIME('text/x-haml', 'haml');
});
